#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <sys/ioctl.h>
#include <poll.h>
#include <signal.h>

#include "./include/common.h"

#define BUFSIZE   32 /* 设置最大偏移量为64, 方便打印完整的内存空间数据*/

#define CMD_TEST0             (_IO('S', 0))
#define CMD_TEST1             (_IOW('S', 1, int))
#define CMD_TEST2             (_IOR('S', 2, int))
#define CMD_TEST3             (_IOW('S', 3, int))

#define CMD_TIMER_SETPERIOD   (_IOW('T', 100, int)) /* 设置定时器周期命令 */
#define CMD_TIMER_OPEN        (_IO('T', 101))       /* 打开内核定时器 */
#define CMD_TIMER_CLOSE       (_IO('T', 102))       /* 关闭内核定时器 */

struct __CMD_TEST{
	int a;
	int b;
	int c;
};

/**
 * @brief  usage_info()
 * @note   打印帮助信息
 * @param [in]
 * @param [out]
 * @retval 
 */
void usage_info(void)
{
	printf("\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
	printf("+       help information  @sumu          +\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");

	printf("help:\n");
	printf("use format: ./app_name /dev/device_name arg1 ... \n");
	printf("            ./app_demo.out /dev/sdevice 1 x data # 从x位置读取 \n");
	printf("            ./app_demo.out /dev/sdevice 2 x data # 从x位置写入 \n");
	printf("            ./app_demo.out /dev/sdevice 3 x data # x为任意值 \n");
    printf("            驱动中buf最大为32字节 \n");
	printf("\n");

	printf("command info:\n");
	printf("  (1)load module  : insmod module_name.ko\n");
	printf("  (2)unload module: rmmod module_name.ko\n");
	printf("  (3)show module  : lsmod\n");
	printf("  (4)view device  : cat /proc/devices\n");
	printf("  (5)create device node: mknod /dev/device_name c major_num secondary_num \n");
	printf("  (6)show device node  : ls /dev/device_name \n");
	printf("  (7)show device vlass  : ls /sys/class \n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
}

void arg_info_print(int argc, char *argv[])
{
    int i = 0;
    PRT("total arg is %d: ", argc);
    for (i = 0; i < argc; i++) 
    {
        printf("argv[%d]=%s ", i, argv[i]);
    }
    printf("\n");
}

int fd = -1;
// SIGIO 信号的信号处理函数
static void func_signo_handle(int sig_num)
{
    int ret = 0;
    char readbuf[BUFSIZE] = {0};

    ret = lseek(fd, 0, SEEK_SET); // 将偏移量设置为距离起始地址 off的位置
    if (ret < 0)
    {
        PRTE("lseek failed!\n");
        return;
    }
    memset(readbuf, 0, sizeof(readbuf));
    ret = read(fd, readbuf, sizeof(readbuf));
    if (ret < 0)
    {
        PRTE("read from %d failed!\n", fd);
    }
    else
    {
        /*  读取成功，打印出读取成功的数据 */
        PRT("read data \"%s\" from %d!\n", readbuf, fd);
    }
}

int main(int argc, char *argv[])
{
    
    int ret = 0;
    char *dev_name = NULL;
    char *p_write_data = NULL;
    unsigned int arg1 = 0;
    unsigned int arg2 = 0;

    char writebuf[BUFSIZE] = {0};

    unsigned int off_end = 0; // 定义读写偏移位置
    unsigned int off_cur = 0; // 定义读写偏移位置
    unsigned int off = 0; // 定义读写偏移位置

    printf("*** Build Time: %s %s,Git Version: %s Git Remote: %s***\n", 
            __DATE__, __TIME__, GIT_VERSION, GIT_PATH);
    /** 
     * 获取传入参数并打印 
     * ./xxx.out /dev/sdevice x x x
     */
    arg_info_print(argc, argv);
    if (argc < 3) 
    {
        usage_info();
        return -1;
    }

    /** 解析参数 */
    dev_name = argv[1];          // 设备节点名称
    arg1 = atoi(argv[2]);  // 1=读，2=写，3=ioctl

    if(argv[3] != NULL && (arg1 == 1 || arg1 == 2))
    {
        arg2 = atoi(argv[3]);  // 读写的时候有效，表示读写的位置
    }
    if(argv[4] != NULL && (arg1 == 2))
    {
        p_write_data = argv[4];     // 写的时候有效，表示写的数据
    }

    /** 打开设备文件 */
    fd = open(dev_name,  O_RDWR); // 使用阻塞标志
    if (fd < 0)
    {
        PRTE("can't open file %s !\n", dev_name);
        return -1;
    }
    off_end = lseek(fd, 0, SEEK_END); // 读取字符设备文件可读写最大的大小

    if (arg1 == 1) /** 从设备节点指定位置读取数据 */
    {
        int flags = 0;
        /* 步骤一：使用signal函数注册SIGIO信号的信号处理函数 */
        signal(SIGIO, func_signo_handle);
        /* 步骤二：设置能接收这个信号的进程 */
        fcntl(fd, F_SETOWN, getpid()); // 设置当前进程接收SIGIO信号
        flags = fcntl(fd, F_GETFL);    // 获取当前的进程状态
        /* 步骤三  开启信号驱动IO 使用fcntl函数的F_SETFL命令打开FASYNC标志 */
        fcntl(fd, F_SETFL, flags | FASYNC); // 设置进程启用异步通知功能
        while (1) 
        {
            sleep(3);
            PRT("wait sigio signal!\n");
        }
    }
    else if (arg1 == 2) /** 从设备节点指定位置写入数据 */
    {
        off = arg2;// 获取读写的偏移位置
        if(off > off_end)
        {
            off = off_end;
        }
        
        if(p_write_data == NULL)
        {
            PRTE("The written data is missing!\n");
            return -1;
        }
        snprintf (writebuf, sizeof(writebuf), "%s", p_write_data);
        ret = lseek(fd, off, SEEK_SET); // 将偏移量设置为距离起始地址 off的位置
        if (ret < 0)
        {
            PRTE("lseek %s %d failed!\n", dev_name, off);
            return -1;
        }

        ret = write(fd, writebuf, strlen(p_write_data));
        if (ret < 0)
        {
            PRTE("write file %s failed!\n", dev_name);
            return -1;
        }
        off_cur = lseek(fd, 0, SEEK_CUR); // 读取当前位置的偏移量
        PRT("write \"%s\" to %s success!off=%d off_cur=%d\n", writebuf, dev_name, off, off_cur);
    }
    else if (arg1 == 3) /** ioctl测试 */
    {
        int val = 0;
        struct __CMD_TEST cmd_test3 = {2, 3, 5};
        ioctl(fd,CMD_TEST0);
        ioctl(fd,CMD_TEST1, 1);
        ioctl(fd,CMD_TEST2, &val);
        printf("val = %d\n", val);

        ioctl(fd,CMD_TEST3, &cmd_test3);
    }
    else if (arg1 == 4) /** ioctl 控制定时器测试 */
    {
        /** 打开一个定时器(默认是5秒超时) */
        PRT("open a kernel timer, default timeout 5s!app will sleep 17s\n");
        ioctl(fd,CMD_TIMER_OPEN);
        sleep(17);//将会出现3次超时

        /** 修改一个定时器(改为2秒超时) */
        PRT("modify a kernel timer, timeout 2s!app will sleep 6s\n");
        ioctl(fd,CMD_TIMER_SETPERIOD, 2*1000);
        sleep(7);//将会出现3次超时

        /** 关闭定时器 */
        PRT("close a kernel timer!app will sleep 3s\n");
        ioctl(fd,CMD_TIMER_CLOSE);
        sleep(3);//将不会出现超时
    }

    /* 关闭设备 */
    ret = close(fd);
    if (ret < 0)
    {
        PRTE("can't close file %s !\n", dev_name);
        return -1;
    }

    return 0;
}

/**
 * @brief  sys_pts_get_ms
 * @note   获取系统时间戳 单位 ms
 * @param [in]
 * @param [out]
 * @retval 
 */
int sys_pts_get_ms(void)
{
    struct timespec tv = {0};
    long long lasttime = 0;

    clock_gettime(CLOCK_MONOTONIC, &tv);
    lasttime = tv.tv_sec * 1000 + tv.tv_nsec / (1000 * 1000);

    return lasttime;

}